﻿using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection.Emit;

namespace PrivateBranch
{
    public class OnlineEngineWrapper
    {
        public OnlineEngineWrapper(Workspace workspace, OnlineMode mode)
        {
            // load Power Tools assembly, cache all types
            var powerToolDir = Environment.GetEnvironmentVariable("TFSPowerToolDir");
            var assembly = Assembly.LoadFile(Path.Combine(powerToolDir, "Microsoft.TeamFoundation.PowerTools.Client.dll"));
            _types = assembly.GetTypes();

            // onlineMode = mode;
            var onlineModeType = _types.First(t => t.Name == "OnlineMode");
            var onlineMode = Enum.ToObject(onlineModeType, (int)mode);

            // _engine = new OnlineEngine(workspace, onlineMode);
            _engineType = _types.First(t => t.Name == "OnlineEngine");
            _engine = Activator.CreateInstance(_engineType, workspace, onlineMode);

            // set up dynamic delegate used to route events
            _messageHandlerType = _types.First(t => t.Name == "MessageEventHandler");
            var routeToStatusWrite = CreateEventRouter("StatusWriteEvent");
            _routeStatusWrite = routeToStatusWrite.CreateDelegate(_messageHandlerType);
            var routeToStatusWriteLine = CreateEventRouter("StatusWriteLineEvent");
            _routeStatusWriteLine = routeToStatusWriteLine.CreateDelegate(_messageHandlerType);

            // fire our class's events when the wrapped engine does
            _swEvent = _engineType.GetEvent("StatusWriteEvent");
            _swEvent.AddEventHandler(_engine, _routeStatusWrite);
            _swlEvent = _engineType.GetEvent("StatusWriteLineEvent");
            _swlEvent.AddEventHandler(_engine, _routeStatusWriteLine);
        }

        ~OnlineEngineWrapper()
        {
            _swEvent.RemoveEventHandler(_engine, _routeStatusWrite);
            _swlEvent.RemoveEventHandler(_engine, _routeStatusWriteLine);
        }

        public void GoOnline(ItemSpec[] itemSpecs, RecursionType recursion, List<string> exclusions)
        {
            // var set = new Set();
            var setType = _types.First(t => t.Name == "Set");
            setType.MakeGenericType(typeof(string));
            var exclusionsSet = Activator.CreateInstance(setType);

            // _engine.GoOnline(itemSpecs, recursion, exclusionsSet);
            var args = new object[] { itemSpecs, recursion, exclusionsSet };
            _engineType.InvokeMember("GoOnline", BindingFlags.InvokeMethod, Type.DefaultBinder, _engine, args);
        }

        public int BatchSize
        {
            get
            {
                return (int) _engineType.InvokeMember("BatchSize", BindingFlags.GetProperty, Type.DefaultBinder, _engine, null);
            }
            set
            {
                _engineType.InvokeMember("BatchSize", BindingFlags.SetProperty, Type.DefaultBinder, _engine, new object[] { value });
            }
        }

        public Dictionary<string, ItemType> Different
        {
            get
            {
                return (Dictionary<string, ItemType>)_engineType.InvokeMember("Different", BindingFlags.GetProperty, Type.DefaultBinder, _engine, null);
            }
        }

        public Dictionary<string, ItemType> LocalOnly
        {
            get
            {
                return (Dictionary<string, ItemType>)_engineType.InvokeMember("LocalOnly", BindingFlags.GetProperty, Type.DefaultBinder, _engine, null);
            }
        }

        public Dictionary<string, ItemType> ServerOnly
        {
            get
            {
                return (Dictionary<string, ItemType>)_engineType.InvokeMember("ServerOnly", BindingFlags.GetProperty, Type.DefaultBinder, _engine, null);
            }
        }

        public OnlineMode Mode
        {
            get
            {
                return (OnlineMode)_engineType.InvokeMember("Mode", BindingFlags.GetProperty, Type.DefaultBinder, _engine, null);
            }
            set
            {
                _engineType.InvokeMember("Mode", BindingFlags.SetProperty, Type.DefaultBinder, _engine, new object[] { value });
            }
        }

        public delegate void MessageEventHandler(object sender, MessageEventArgs e);
        public event MessageEventHandler StatusWriteEvent;
        public event MessageEventHandler StatusWriteLineEvent;

        private object _engine;
        private Type _engineType;
        private Type _messageHandlerType;
        private Type[] _types;
        private Delegate _routeStatusWrite;
        private Delegate _routeStatusWriteLine;
        private EventInfo _swEvent;
        private EventInfo _swlEvent;

        private string GetMessage(object messageEventArgs)
        {
            return (string)_messageHandlerType.InvokeMember("Message", BindingFlags.GetProperty, Type.DefaultBinder, messageEventArgs, null);
        }

        // http://msdn.microsoft.com/en-us/library/ms228976.aspx#procedureSection1
        private Type[] GetDelegateParameterTypes(Type d)
        {
            if (d.BaseType != typeof(MulticastDelegate))
                throw new ArgumentException("Not a delegate.", d.Name);

            MethodInfo invoke = d.GetMethod("Invoke");
            if (invoke == null)
                throw new ArgumentException("Not a delegate.", d.Name);

            ParameterInfo[] parameters = invoke.GetParameters();
            Type[] typeParameters = new Type[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {
                typeParameters[i] = parameters[i].ParameterType;
            }
            return typeParameters;
        }
        
        private DynamicMethod CreateEventRouter(string eventToFire)
        {
            //private void Route(object sender, [other guy's] MessageEventArgs args)
            //{
            //    StatusWriteEvent(sender, new MessageEventArgs( GetMessage(args) ));
            //}

            var method = new DynamicMethod("RouterTo" + eventToFire.ToString(), null, GetDelegateParameterTypes(_messageHandlerType));
            var il = method.GetILGenerator();

            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, string.Format("class PrivateBranch.OnlineEngineWrapper/MessageEventHandler PrivateBranch.OnlineEngineWrapper::{0}", eventToFire));
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Call, "instance string PrivateBranch.OnlineEngineWrapper::GetMessage(object)");
            il.Emit(OpCodes.Newobj, "instance void PrivateBranch.MessageEventArgs::.ctor(string)");
            il.Emit(OpCodes.Callvirt, "instance void PrivateBranch.OnlineEngineWrapper/MessageEventHandler::Invoke(object, class PrivateBranch.MessageEventArgs)");
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ret);

            return method;
        }

        
    }




    public class MessageEventArgs : EventArgs
    {
        public MessageEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; set; }
    }

    public enum OnlineMode
    {
        LocalAndDifferentOnly,
        Complete,
        CompleteMD5
    }
}
